<?php

namespace Rum\Router;

use Fend\Di;
use Fend\Router\Router;
use Fend\Router\RouterException;
use Rum\Router\Annotation\RouterParser;

class RumRouter extends Router
{
    // 路由树
    private $trees = null;
    // 实例对象
    private static $inst = null;
    // 配置
    private $cfg = null;

    /**
     * @method: RumRouter
     * @return: Router
     * @author: liumurong  <liumurong1@100tal.com>
     * @Date: 2019-11-21 10:35:59
     */
    public function __construct()
    {
        $this->trees = [];
    }

    /**
     * 获取对象实例
     * @return: Router $inst 
     * @author: liumurong  <liumurong1@100tal.com>
     * @Date: 2019-11-11 10:20:01
     */
    public static function instance()
    {
        if (self::$inst == null) {
            self::$inst = new self();
        }
        return self::$inst;
    }

    /**
     * 初始化路由
     * @param {array} $cfg 参数以及路由配置 
     * @param {array} $domain 域名
     * @return: void
     * @author: liumurong  <liumurong1@100tal.com>
     * @Date: 2019-11-08 11:12:48
     */
    public function initRouter($cfg, $domain)
    {
        $this->cfg = $cfg;
        // 1. 检查是否存在缓存
        if (!empty($cfg['enableCache'])) {
            if (empty($cfg['cachePath']) || !file_exists($cfg['cachePath'])) {
                throw new RouterException('缓存目录不存在');
                return $this;
            }
            $cacheFile = Util::path_join($cfg['cachePath'], 'rum.rc');
            if (file_exists($cacheFile)) {
                $ctx = file_get_contents($cacheFile);
                if (!$ctx) {
                    throw new RouterException('路由缓存读取失败');
                    return $this;
                }
                $trees = unserialize($ctx);
                if (!$trees) {
                    throw new RouterException('路由缓存解析失败');
                    return $this;
                }
                $this->trees = $trees;
                // 域名不存在则继续解析
                if (!empty($this->trees[$domain])) {
                    return $this;
                }
            }
        }
        // 2. 是否启用注解路由
        if (!empty($cfg['enableControllerAnnotation'])) {
            if (empty($cfg['controllerNamespace'])) {
                throw new RouterException('启用路由注解时必须指定控制器根命名空间');
                return $this;
            }
            if (empty($cfg['controllerBasePath'])) {
                throw new RouterException('启用路由注解时必须指定控制器根路径');
                return $this;
            }
            if (empty($cfg['middlewareNamespace'])) {
                throw new RouterException('启用路由注解时必须指定中间件根命名空间');
                return $this;
            }
            if (empty($cfg['middlewareBasePath'])) {
                throw new RouterException('启用路由注解时必须指定中间件根路径');
                return $this;
            }
            $p = new RouterParser([
                'controllerNamespace' => $cfg['controllerNamespace'],
                'controllerBasePath' => $cfg['controllerBasePath'],
                'middlewareNamespace' => $cfg['middlewareNamespace'],
                'middlewareBasePath' => $cfg['middlewareBasePath'],
                'ignoreAnnotations' => $cfg['ignoreAnnotations'],
                'globalMiddleware' => $cfg['globalMiddleware'] ?? [],
                'enableClosure' => $cfg['enableClosure'],
            ]);
            $groups = $p->handle();
            $this->handleRouter($domain, $groups);
        }
        // 3. 解析配置路由
        if (!empty($cfg['router'])) {
            foreach ($cfg['router'] as $router) {
                if (is_array($router[2])) {
                    $this->addRoute($domain, $router[0], $router[1], ...$router[2]);
                    continue;
                }
                $this->addRoute($domain, $router[0], $router[1], $router[2]);
            }
        }

        // 4. 缓存
        if (!empty($cfg['enableCache']) && empty($cfg['enableClosure'])) {
            if (empty($cfg['cachePath']) || !file_exists($cfg['cachePath'])) {
                throw new RouterException('无法缓存路由，缓存目录不存在');
                return $this;
            }
            // 缓存文件不存在时缓存路由
            $cacheFile = Util::path_join($cfg['cachePath'], 'rum.rc');
            if (!file_put_contents($cacheFile, serialize($this->trees))) {
                throw new RouterException('路由缓存写入失败');
            }
        }
        return $this;
    }
    /**
     * @param {string} $domain 域名
     * @param {string} $method 请求方法
     * @param {string} $path 请求的路径
     * @param {type} 
     * @return: array
     * @author: liumurong  <liumurong1@100tal.com>
     * @Date: 2019-11-08 11:16:59
     */
    public function dispatch($domain, $method, $path)
    {
        try {
            if (empty($this->trees[$domain])) {
                throw new RouterException("404 " . $path . " map not found", 404); // 未包含请求域
                return;
            }
            $rootTree = $this->trees[$domain];
            // 路由存在
            if (!empty($rootTree[$method])) {
                $handle = $rootTree[$method]->getValue($path);
                if (!empty($handle['handles'])) {
                    if (count($handle['params']) > 0) {
                        // 记录URL中的参数
                        $req = Di::factory()->getRequest();
                        foreach ($handle['params'] as $k => $v) {
                            $req->setQueryString($v, $k);
                        }
                    }
                    $result = null;     // 只记录最后一次结果(之前都是中间件)
                    foreach ($handle['handles'] as $fn) {
                        if (is_callable($fn)) {
                            $req = Di::factory()->getRequest();
                            $res = Di::factory()->getResponse();
                            $result = $fn($req, $res);
                            continue;
                        }
                        $result = $this->str2Func($fn)();
                    }
                    return $result;
                }
            }
            // 检测是否包含同PATH但是METHOD不同的路由，提示405错误
            if (!empty($this->cfg['handleMethodNotAllowed'])) {
                foreach ($rootTree as $m => $tree) {
                    if ($m != $method) {
                        $handle = $tree->getValue($path);
                        if (!empty($handle) && !empty($handle['handles'])) {
                            throw new RouterException("405 Method Not Allowed", 405);
                            return;
                        }
                    }
                }
            }
            // 未包含路由，提示404错误。
            throw new RouterException("404 " . $path . " map not found", 404);
        } catch (RouterException $e) {
            throw $e;
        } catch (\Throwable $th) {
            // throw new RouterException("Router Config error on handle." . $path, -108);
            throw $th;
        }
    }

    /**
     * 将解析控制器路由得到的参数转化为真正的路由
     * @param {string} $domain 域名
     * @param {array} $groupItem 解析控制器得到的路由组
     * @author: liumurong  <liumurong1@100tal.com>
     * @Date: 2019-10-16 09:24:35
     */
    private function handleRouter($domain, $groupItems)
    {
        foreach ($groupItems as $item) {
            $group = $this->group($domain, $item->getPrefix(), ...$item->getMiddlewares());
            foreach ($item->getRouters() as $router) {
                $methods = explode(',', $router['methods']);
                foreach ($methods as $method) {
                    $group->handle($method, $router['path'], $router['handle']);
                }
            }
        }
    }

    /**
     * 创建新的路由组
     * @param string $path 路径
     * @param func[] $middleware 中间件集合
     * @return RouterGroup 
     */
    public function group($domain, $path, ...$middleware)
    {
        $g = new RouterGroup($domain, $path, $this);
        $g->use(...$middleware);
        return $g;
    }

    /**
     * 添加路由
     * @param string $domain 域名
     * @param string $method 方法名
     * @param string $path 路径
     * @param func[] $handles 中间件集合
     * @return 
     */
    public function addRoute($domain, $method, $path, ...$handles)
    {
        if (!in_array($method, Method::S)) {
            throw new RouterException('不支持的请求方法');
            return;
        }
        if ($path[0] != '/') {
            throw new RouterException('路由路径必须以/开头');
            return;
        }
        if (!isset($this->trees[$domain])) {
            $this->trees[$domain] = [];
        }
        if (empty($this->trees[$domain][$method])) {
            $this->trees[$domain][$method] = new Node();
        }
        $this->trees[$domain][$method]->addRoute($path, $handles);
    }

    /**
     * 将方法路径转成匿名函数
     * @param string $funcPath
     * @return: function
     * @author: liumurong  <liumurong1@100tal.com>
     * @Date: 2019-11-21 11:19:15
     */
    private function str2Func($funcPath)
    {
        $handler = explode("@", $funcPath);
        if (count($handler) != 2) {
            throw new RouterException("Router Config error on handle.Handle only support two parameter with @" . $funcPath, -105);
        }
        $className = $handler[0];
        $func = $handler[1];
        if (!class_exists($className)) {
            throw new RouterException("Router $funcPath Handle definded Class Not Found", -106);
        }
        if (!method_exists($className, $func)) {
            throw new RouterException("Router $funcPath Handle definded $func Method Not Found", -107);
        }
        return function () use ($className, $func) {
            return $this->invokeFunc($className, $func, []);
        };
    }
}
